#include "mipi_csi.h"

extern u32 get_ahb_phys(void);
extern u32 get_ahb_virt(void);
extern u32 get_ahb_size(void);

extern u32 get_apb_phys(void);
extern u32 get_apb_virt(void);
extern u32 get_apb_size(void);

#define WRREG(addr,value)    *(volatile unsigned long*)(addr)=(value)
#define RDREG(addr)          *(volatile unsigned long*)(addr)

#define MIPI_CSI_REG_BASE            0x80019000
#define CNFG_ADDR_EXT                0x80118000
#define CNFG_VIN_INPUT               0x80110004
#define IOCTRL_GPIO_REG15            0x8017026C

//CSI register offset
#define MIPI_CSI_VERSION                 (0x00)
#define MIPI_CSI_N_LANES                 (0x04)
#define MIPI_CSI_CSI2_RESETN             (0x08)
#define MIPI_CSI_INT_ST_MAIN             (0x0C)
#define MIPI_CSI_DATA_IDS_1              (0x10)
#define MIPI_CSI_DATA_IDS_2              (0x14)
                                         
#define MIPI_CSI_PHY_SHUTDOWNZ           (0x40)
#define MIPI_CSI_DPHY_RSTZ               (0x44)
#define MIPI_CSI_PHY_RX                  (0x48)
#define MIPI_CSI_PHY_STOPESTATE          (0x4C)
                                         
#define MIPI_CSI_PHY_TEST_CTRL0          (0x50)
#define MIPI_CSI_PHY_TEST_CTRL1          (0x54)
                                         
#define MIPI_CSI_PHY2_TEST_CTRL0         (0x58)
#define MIPI_CSI_PHY2_TEST_CTRL1         (0x5C)
                                         
#define MIPI_CSI_IPI_MODE                (0x80)
#define MIPI_CSI_IPI_VCID                (0x84)
#define MIPI_CSI_IPI_DATA_TYPE           (0x88)
                                         
#define MIPI_CSI_IPI_HSA_TIME            (0x90)
#define MIPI_CSI_IPI_HBP_TIME            (0x94)
#define MIPI_CSI_IPI_HSD_TIME            (0x98)
#define MIPI_CSI_IPI_HLINE_TIME          (0x9C)
                                         
#define MIPI_CSI_IPI_VSA_LINES           (0xB0)
#define MIPI_CSI_IPI_VBP_LINES           (0xB4)
#define MIPI_CSI_IPI_VFP_LINES           (0xB8)
#define MIPI_CSI_IPI_VACTIVE_LINES       (0xBC)

#define MIPI_CSI_INT_ST_PHY_FATAL        (0xE0)
#define MIPI_CSI_INT_MSK_PHY_FATAL       (0xE4)
#define MIPI_CSI_INT_FORCE_PHY_FATAL     (0xE8)
#define MIPI_CSI_INT_ST_PKT_FATAL        (0xF0)
#define MIPI_CSI_INT_ST_MSK_PKT_FATAL    (0xF4)
#define MIPI_CSI_INT_FORCE_PKT_FATAL     (0xF8)
#define MIPI_CSI_INT_ST_FRAME_FATAL      (0x100)
#define MIPI_CSI_INT_MSK_FRAME_FATAL     (0x104)
#define MIPI_CSI_INT_FORCE_FRAME_FATAL   (0x108)
#define MIPI_CSI_NT_ST_PHY               (0x110)
#define MIPI_CSI_INT_MSK_PHY             (0x114)
#define MIPI_CSI_INT_FORCE_PHY           (0x118)
#define MIPI_CSI_INT_ST_PKT              (0x120)
#define MIPI_CSI_INT_MSK_PKT             (0x124)
#define MIPI_CSI_INT_FORCE_PKT           (0x128)
#define MIPI_CSI_INT_ST_LINE             (0x130)
#define MIPI_CSI_INT_MSK_LINE            (0x134)
#define MIPI_CSI_INT_FORCE_LINE          (0x138)
#define MIPI_CSI_INT_ST_IPI              (0x140)
#define MIPI_CSI_INT_MSK_IPI             (0x144)
#define MIPI_CSI_INT_FORCE_IPI           (0x148)

static unsigned int csi_core_write_part(unsigned int address, unsigned int data, unsigned int shift, unsigned int width)
{

	unsigned int mask = (1 << width) - 1;
	unsigned int temp = RDREG(address);
	temp &= ~(mask << shift);
	temp |= (data & mask) << shift;
	return WRREG(address, temp);
}

static unsigned int mipi_csi_phy_read(unsigned int base_addr,unsigned int address)
{
    unsigned int value = address + 0x10000;
    WRREG(base_addr+MIPI_CSI_PHY_TEST_CTRL1,value);msleep(2);
    csi_core_write_part(base_addr+MIPI_CSI_PHY_TEST_CTRL0,1,1,1);msleep(2);
    csi_core_write_part(base_addr+MIPI_CSI_PHY_TEST_CTRL0,0,1,1);msleep(2);
    value = RDREG(base_addr+MIPI_CSI_PHY_TEST_CTRL1);msleep(2);
    WRREG(base_addr+MIPI_CSI_PHY_TEST_CTRL1,value&0xFFFEFFFF);msleep(2);
    value = (value >>8)&0xFF;
    return value;
}

static void mipi_csi_phy_write(unsigned int base_addr,unsigned int address,unsigned int data)
{
    unsigned int value = address + 0x10000;
    WRREG(base_addr+MIPI_CSI_PHY_TEST_CTRL1,value);msleep(2);
    csi_core_write_part(base_addr+MIPI_CSI_PHY_TEST_CTRL0,1,1,1);msleep(2);
    csi_core_write_part(base_addr+MIPI_CSI_PHY_TEST_CTRL0,0,1,1);msleep(2);
    WRREG(base_addr+MIPI_CSI_PHY_TEST_CTRL1,data);msleep(2);
    csi_core_write_part(base_addr+MIPI_CSI_PHY_TEST_CTRL0,1,1,1);msleep(2);
    csi_core_write_part(base_addr+MIPI_CSI_PHY_TEST_CTRL0,0,1,1);msleep(2);
}

static void mipi_csi_Dphy_freq_select(unsigned int csi_reg_base, MIPI_CSI_DPHY_FREQ_RANGE_E range)
{
    unsigned char val;
    val = range << 1;
    printk("mipi_csi_Dphy_freq_select reg0x44 write value: 0x%02x\n", val);
    mipi_csi_phy_write(csi_reg_base, 0x44, val);
    //mipi_csi_phy_write(csi_reg_base, 0x31,0xbf);
}

void set_mipi_in_enable(void)
{    
    unsigned int csi_reg_base = 0; 
    unsigned int temp = 0;

    // address select vin
    csi_reg_base = get_apb_virt() + (CNFG_ADDR_EXT - get_apb_phys());
    WRREG(csi_reg_base, 0x00000100);
	
    // mipi input enable
    csi_reg_base = get_apb_virt() + (CNFG_VIN_INPUT - get_apb_phys());
    temp = RDREG(csi_reg_base);
    WRREG(csi_reg_base, temp|0x4000);
   
    // set pclk pin pwm
    csi_reg_base = get_apb_virt() + (IOCTRL_GPIO_REG15 - get_apb_phys());    
    temp = RDREG(csi_reg_base);
    temp &= ~(0x7f<<24);
    WRREG(csi_reg_base, (temp|(0x12<<24))); msleep(5);
    WRREG(csi_reg_base, (temp|(0x22<<24))); msleep(5);
    WRREG(csi_reg_base, (temp|(0x12<<24))); msleep(5);
    WRREG(csi_reg_base, (temp|(0x22<<24))); msleep(5);   
    WRREG(csi_reg_base, (temp|(0x12<<24))); msleep(5);
    WRREG(csi_reg_base, (temp|(0x22<<24))); msleep(5);   
     
    return;
}

int sgks_mipi_csi_phy_init(SGKS_MIPI_CSI_CONFIG_T *configParam)
{
	unsigned int value = 0;
	unsigned int csi_reg_base = 0;
    //unsigned long delay;
    unsigned int mask;
    unsigned int wait_lp11_counter = 0;

    set_mipi_in_enable();

    //because kernel already distribution the AHB and APB virtual address, so here do not ioremap
    csi_reg_base = get_apb_virt() + (MIPI_CSI_REG_BASE - get_apb_phys());//(unsigned int)ioremap(MIPI_CSI_REG_BASE, 512);

    value = RDREG(csi_reg_base + MIPI_CSI_VERSION);
    if(value != 0x3131312a)
    {
        printk("version not match\n");
	    return -1;
    }

    //release host from reset
    csi_core_write_part(csi_reg_base + MIPI_CSI_CSI2_RESETN,0,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_CSI2_RESETN,1,0,1);msleep(10);

   //set or clear symopsys D-PHY reset
    csi_core_write_part(csi_reg_base + MIPI_CSI_PHY_SHUTDOWNZ,0,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_DPHY_RSTZ,0,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_PHY_SHUTDOWNZ,1,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_DPHY_RSTZ,1,0,1);msleep(10);

    //Release Synopsys D-PHY test codes from reset
    csi_core_write_part(csi_reg_base + MIPI_CSI_PHY_TEST_CTRL0,1,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_PHY_TEST_CTRL0,0,0,1);msleep(10);

    //configure the D-PHY frequency range
    mipi_csi_Dphy_freq_select(csi_reg_base, configParam->dphy_freq_range);msleep(2);
    printk("0x44: 0x%x\n", mipi_csi_phy_read(csi_reg_base, 0x44));
    printk("0x34: 0x%x\n", mipi_csi_phy_read(csi_reg_base, 0x34));
    printk("0x17: 0x%x\n", mipi_csi_phy_read(csi_reg_base, 0x17));
    printk("0x18: 0x%x\n", mipi_csi_phy_read(csi_reg_base, 0x18));
    //msleep(100);
    //mipi_csi_dhpy_calibration(csi_reg_base);

    /*
    config data lane :
    0: 1 Data Lane (lane0)
    1: 2 Data Lane (lane0 lane1)
    2: 3 Data Lane (lane0 lane1 lane2)
    3: 4 Data Lane (lane0 lane1 lane2 lane3)
    */
    //csi_core_write_part(csi_reg_base + MIPI_CSI_N_LANES,1,0,3) ;  // 2 lanes
    csi_core_write_part(csi_reg_base + MIPI_CSI_N_LANES,configParam->mipi_lanes,0,3);msleep(1);
    //debug_printk("configParam->mipi_lanes:%d\n", configParam->mipi_lanes);
   //set or clear symopsys D-PHY reset
    csi_core_write_part(csi_reg_base + MIPI_CSI_PHY_SHUTDOWNZ,0,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_PHY_SHUTDOWNZ,1,0,1);msleep(10);


    //release host from reset
    csi_core_write_part(csi_reg_base + MIPI_CSI_CSI2_RESETN,0,0,1);msleep(10);
    csi_core_write_part(csi_reg_base + MIPI_CSI_CSI2_RESETN,1,0,1);msleep(10);

    //wait clock lane and data lane stop
    switch(configParam->mipi_lanes)
    {
        case MIPI_CSI_ACTIVE_LANE_1:
        {
            mask = 0x10001;
            break;
        }
        case MIPI_CSI_ACTIVE_LANE_2:
        {
            mask = 0x10003;
            break;
        }
        case MIPI_CSI_ACTIVE_LANE_3:
        {
            mask = 0x10007;
            break;
        }
        case MIPI_CSI_ACTIVE_LANE_4:
        {
            mask = 0x1000F;
            break;
        }

        default : 
        {
            mask = 0x10003;
            break;
        }            
    }
    value = 0;
    while((value&mask) != mask)
    {
        wait_lp11_counter++;
        value = RDREG(csi_reg_base + MIPI_CSI_PHY_STOPESTATE);

        if (wait_lp11_counter>200)
            return -1;
        printk("MIPI_CSI_PHY_STOPESTATE:0x%08x\n", value);
    }

    //configure IPI:
    WRREG(csi_reg_base + MIPI_CSI_IPI_VCID,configParam->ipi_vcid);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_MODE,(configParam->ipi_mode)|(configParam->ipi_color_mode_bits<<8));msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_DATA_TYPE, configParam->ipi_data_type);msleep(1);

    WRREG(csi_reg_base + MIPI_CSI_IPI_HSA_TIME, configParam->video_hsa_time);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_HBP_TIME, configParam->video_hbp_time);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_HSD_TIME, configParam->video_hsd_time);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_HLINE_TIME, configParam->video_hline_time);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_VSA_LINES, configParam->video_vsa_lines);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_VBP_LINES, configParam->video_vbp_lines);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_VFP_LINES, configParam->video_vfp_lines);msleep(1);
    WRREG(csi_reg_base + MIPI_CSI_IPI_VACTIVE_LINES, configParam->video_vactive_lines);msleep(1);

    return 0;
}


int sgks_mipi_csi_phy_init_stage2(void)
{
#define WAIT_CLOCK_TIMEOUT 5000
    unsigned int value = 0;
    unsigned int csi_reg_base = 0;
    unsigned long delay;
    //because kernel already distribution the AHB and APB virtual address, so here do not ioremap
    csi_reg_base = get_apb_virt() + (MIPI_CSI_REG_BASE - get_apb_phys());//(unsigned int)ioremap(MIPI_CSI_REG_BASE, 512);

    //wait clock lane actively receiving a DDR clock
    printk(">>>>>>>>>>>>>>>>>wait mipi clock lane active\n");
    delay = jiffies + msecs_to_jiffies(WAIT_CLOCK_TIMEOUT);
    value = 0;
    while((value & 0x20000) != 0x20000)
    {
        value = RDREG(csi_reg_base + MIPI_CSI_PHY_RX);
        printk("wait mipi clock lane active\n");
        if(time_after(jiffies, delay))
        {
            printk("error: wait mipi clock lane active time out;%dms\n", WAIT_CLOCK_TIMEOUT);
            return -1;
        }
    }

#if 0
    delay = jiffies + msecs_to_jiffies(WAIT_CLOCK_TIMEOUT);
    value = 1;
    while (value)
    {
        value = RDREG(csi_reg_base + MIPI_CSI_INT_ST_PHY_FATAL);
        value |= RDREG(csi_reg_base + MIPI_CSI_INT_ST_PKT_FATAL);
        value |= RDREG(csi_reg_base + MIPI_CSI_INT_ST_FRAME_FATAL);
        value |= RDREG(csi_reg_base + MIPI_CSI_INT_ST_PKT);
        value |= RDREG(csi_reg_base + MIPI_CSI_NT_ST_PHY);
        value |= RDREG(csi_reg_base + MIPI_CSI_INT_ST_LINE);
        value |= RDREG(csi_reg_base + MIPI_CSI_INT_ST_IPI);
       
        printk("read mipi csi state\n");
        if(time_after(jiffies, delay))
        {
            printk("error: wait mipi state;%dms\n", WAIT_CLOCK_TIMEOUT);
            return -1;
        }        
    }
#else    
    while(RDREG(csi_reg_base + MIPI_CSI_INT_ST_PHY_FATAL));

    while(RDREG(csi_reg_base + MIPI_CSI_INT_ST_PKT_FATAL));

    while(RDREG(csi_reg_base + MIPI_CSI_INT_ST_FRAME_FATAL));

    while(RDREG(csi_reg_base + MIPI_CSI_INT_ST_PKT));

    while(RDREG(csi_reg_base + MIPI_CSI_NT_ST_PHY));

    while(RDREG(csi_reg_base + MIPI_CSI_INT_ST_LINE));

    while(RDREG(csi_reg_base + MIPI_CSI_INT_ST_IPI));
#endif
    printk(">>>>>>>>>>>>>>>>>mipi_csi_phy_init done\n");

    return 0;
}